// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
import {
    Api,
    BrushKind,
    ElementInformation,
    GradientStop,
    PreviewData,
    PropertyInformation,
    PropertyValue,
    PropertyValueKind,
    PropertyValueTable,
} from "./api.slint";
import { StatusLineApi } from "components/status-line.slint";

export enum WidgetMode { edit, preview, color-stop }

export enum PickerTab {
    color,
    gradient,
    css-color,
    globals
}

// Color and Brush properties are related, but while a <brush> can be a color,
// a <color> property cannot be a brush. This property type is used to ensure
// <color> properties do not show the more complex brush editor.
export enum BrushPropertyType {
    color,
    brush,
}

export enum BrushMode {
    color,
    conic,
    linear,
    radial,
    css-color,
    code,
}

export enum GradientType {
    conic,
    linear,
    radial,
}

export enum ColorCodeType {
    other,
    css-color,
    global
}

export global WindowGlobal {
    in-out property <length> window-width;
    in-out property <length> window-height;
}

export global TableData {
    callback populate-table(property-group-id: string, preview-data: PreviewData);
    callback show-brush-editor(selected-row: int, selected-col: int);
    callback set-color-preview(value: string);
    out property <string> property-group-id;
    out property <PreviewData> preview-data;
    out property <PropertyValueTable> current-table;
    property <string> possible-error;

    property <int> selected-row;
    property <int> selected-col;

    show-brush-editor(selected-row, selected-col) => {
        self.selected-row = selected-row;
        self.selected-col = selected-col;
        WindowManager.show-floating-preview-widget(self.property-group-id, self.preview-data, self.current-table.values[selected-row][selected-col]);
    }

    populate-table(property-group-id, preview-data) => {
        self.property-group-id = property-group-id;
        self.preview-data = preview-data;
        self.current-table = Api.get-property-value-table(property-group-id, preview-data.name);
    }

    set-color-preview(value) => {
        self.current-table.values[self.selected-row][self.selected-col].was-edited = true;
        self.current-table.values[self.selected-row][self.selected-col].edited-value = value;

        self.possible_error = Api.set-property-value-table(root.property-group-id, root.preview-data.name, root.current-table.values, root.current-table.is-array);
        StatusLineApi.help-text = self.possible-error;
        if self.possible-error == "" {
            self.populate-table(root.property-group-id, root.preview-data);
        }
    }
}

export global PickerData {
    out property <PickerTab> active-tab: PickerTab.color;
    out property <BrushPropertyType> picker-mode: color;
    out property <BrushMode> brush-mode: color;
    // Used to remember the last used gradient type so it won't change if the user
    // switches Picker tab
    in-out property <GradientType> last-used-gradient-type: linear;

    in-out property <float> hue;
    in-out property <float> saturation;
    in-out property <float> value;
    // alpha is an int, instead of float to help snap values to whole percentage numbers
    in-out property <int> alpha;
    out property <color> current-color: hsv(hue, saturation, value, alpha / 100);

    in-out property <BrushKind> current-brush-kind;
    // As the brush can be updated via the Api.move-gradient-stop function it won't trigger a reactive update.
    // So we need to keep track of the current brush ourselves and update it with update-brush();
    out property <brush> current-brush;
    in-out property <[GradientStop]> current-gradient-stops;
    in-out property <float> current-angle;
    in-out property <int> current-stop-index: 0;
    property <float> current-stop-position: current-gradient-stops[current-stop-index].position;
    property <color> current-stop-color: current-gradient-stops[current-stop-index].color;

    callback rebuild-gradient-stops();
    callback set-active-tab(picker-tab: PickerTab);
    callback set-gradient-type(gradient-type: GradientType);
    callback reset();

    changed current-stop-index => {
        update-brush();
        if WindowManager.showing-color-stop-picker {
            set-current-stop-as-color();
        }
    }

    changed current-color => {
        update-brush();
        if WindowManager.showing-color-stop-picker {
            current-gradient-stops[current-stop-index].color = current-color;
        } else if WindowManager.widget-mode == WidgetMode.preview && current-brush-kind == BrushKind.solid {
            WindowManager.update-preview-value(Api.color-to-data(root.current-color).text);
        }
    }

    changed current-brush-kind => {
        update-brush();
        if WindowManager.widget-mode == WidgetMode.preview {
            WindowManager.update-brush-preview();
        }
    }

    changed current-angle => {
        if WindowManager.widget-mode == WidgetMode.preview {
            update-brush();
            WindowManager.update-brush-preview();
        }
    }

    changed current-stop-position => {
        update-brush();
        WindowManager.update-brush-preview();
    }

    changed current-stop-color => {
        update-brush();
        WindowManager.update-brush-preview();
    }

    changed active-tab => {
        WindowManager.hide-color-stop-picker();
    }

    rebuild-gradient-stops => {
        current-gradient-stops = Api.clone-gradient-stops(current-gradient-stops);
        update-brush();
        WindowManager.update-brush-preview();
    }

    set-active-tab(picker-tab) => {
        active-tab = picker-tab;
        if picker-tab == PickerTab.color {
            current-brush-kind = BrushKind.solid;
            brush-mode = BrushMode.color;
        }
        if picker-tab == PickerTab.gradient {
            if last-used-gradient-type == GradientType.radial {
                current-brush-kind = BrushKind.radial;
                brush-mode = BrushMode.radial;
            } else if last-used-gradient-type == GradientType.conic {
                current-brush-kind = BrushKind.conic;
                brush-mode = BrushMode.conic;
            } else {
                current-brush-kind = BrushKind.linear;
                brush-mode = BrushMode.linear;
            }
        }
    }

    set-gradient-type(gradient-type) => {
        last-used-gradient-type = gradient-type;
        if gradient-type == GradientType.linear {
            current-brush-kind = BrushKind.linear;
            brush-mode = BrushMode.linear;
        } else if gradient-type == GradientType.conic {
            current-brush-kind = BrushKind.conic;
            brush-mode = BrushMode.conic;
        } else {
            current-brush-kind = BrushKind.radial;
            brush-mode = BrushMode.radial;
        }
    }

    reset => {
        brush-mode = BrushMode.color;
        active-tab = PickerTab.color;
        picker-mode = BrushPropertyType.color;
        last-used-gradient-type = GradientType.linear;
    }

    public function set-current-stop-as-color() {
        set-color(current-gradient-stops[current-stop-index].color);
    }

    public function update-brush() {
        self.current-brush = Api.create-brush(current-brush-kind, current-angle, current-color, current-gradient-stops);
    }

    function set-svg-or-global-tab(property-value: PropertyValue) {
        if PickerData.get-color-code-type(property-value) == ColorCodeType.css-color {
            set-active-tab(PickerTab.css-color);
        } else {
            set-active-tab(PickerTab.globals);
        }
    }

    public function init-with-property-value(property-value: PropertyValue) {
        PickerData.current-brush-kind = property-value.brush-kind;
        current-gradient-stops = Api.clone-gradient-stops(property-value.gradient-stops);
        current-angle = property-value.value-float;
        brush-mode = property-value.brush-kind == BrushKind.solid ? BrushMode.color : property-value.brush-kind == BrushKind.linear ? BrushMode.linear : BrushMode.radial;

        if property-value.kind == PropertyValueKind.color {
            picker-mode = BrushPropertyType.color;
            if PickerData.get-color-code-type(property-value) == ColorCodeType.other {
                set-active-tab(PickerTab.color);
            } else {
                set-svg-or-global-tab(property-value);
            }
        }

        if property-value.kind == PropertyValueKind.brush {
            picker-mode = BrushPropertyType.brush;
            if PickerData.get-color-code-type(property-value) == ColorCodeType.other {
                if property-value.brush-kind == BrushKind.solid {
                    set-active-tab(PickerTab.color);
                } else {
                    set-active-tab(PickerTab.gradient);
                }
            } else {
                set-svg-or-global-tab(property-value);
            }
        }

        if property-value.brush-kind == BrushKind.solid {
            if property-value.code == "" {
                PickerData.set-default-color();
            } else {
                PickerData.set-color(property-value.value-brush);
            }
        } else {
            PickerData.set-color(PickerData.current-gradient-stops[PickerData.current-stop-index].color);
            if property-value.brush-kind == BrushKind.radial {
                set-gradient-type(GradientType.radial)
            }
            if property-value.brush-kind == BrushKind.linear {
                set-gradient-type(GradientType.linear)
            }
            if property-value.brush-kind == BrushKind.conic {
                set-gradient-type(GradientType.conic)
            }
        }

        update-brush();
    }

    public function set-color(color: color) {
        PickerData.hue = color.to-hsv().hue;
        PickerData.saturation = color.to-hsv().saturation;
        PickerData.value = color.to-hsv().value;
        PickerData.alpha = color.to-hsv().alpha * 100;
    }

    public function set-default-color() {
        if Api.recent-colors.length > 0 {
            set-color(Api.recent-colors[0]);
        } else {
            set-color(#2479f4);
        }
    }

    pure public function get-color-code-type(property-value: PropertyValue) -> ColorCodeType {
        if property-value.value-string == "" {
            return ColorCodeType.other;
        }
        if Api.is-css-color(property-value.code) {
            return ColorCodeType.css-color;
        } else {
            return ColorCodeType.global;
        }
    }
}

export global WindowManager {
    out property <bool> showing-color-picker: false;
    out property <bool> showing-table-editor: false;
    out property <bool> showing-color-stop-picker: false;
    out property <WidgetMode> widget-mode: edit;

    property <ElementInformation> current-element-information;
    out property <PropertyInformation> current-property-information;
    in-out property <string> current-property-container-id;
    in-out property <PreviewData> current-preview-data;
    property component-factory <=> Api.preview-data;

    property <string> possible_error;
    property <string> brush-string;

    callback show-floating-widget(property-information: PropertyInformation, element-information: ElementInformation);
    callback show-floating-preview-widget(property-container-id: string, preview-data: PreviewData, property-value: PropertyValue);
    callback show-floating-table-editor(property-group-id: string, preview-data: PreviewData);
    callback hide-floating-widget();
    callback hide-floating-color-widget();
    callback apply-current-value(value: string);
    callback update-preview-value(value: string);
    callback update-brush-preview();
    callback show-color-stop-picker();
    callback hide-color-stop-picker();

    show-floating-widget(property-information, element-information) => {
        widget-mode = WidgetMode.edit;
        current-property-information = property-information;
        current-element-information = element-information;

        PickerData.init-with-property-value(current-property-information.value);
        showing-color-picker = true;
    }

    hide-floating-widget => {
        showing-color-picker = false;
        showing-table-editor = false;
        showing-color-stop-picker = false;
        current-element-information = { };
        current-property-information = { };
        widget-mode = WidgetMode.edit;
        PickerData.reset()
    }

    show-floating-preview-widget(property-container-id, preview-data, property-value) => {
        current-property-container-id = property-container-id;
        current-preview-data = preview-data;
        widget-mode = WidgetMode.preview;

        PickerData.init-with-property-value(property-value);
        showing-color-picker = true;
    }

    show-floating-table-editor(property-group-id, preview-data) => {
        TableData.populate-table(property-group-id, preview-data);
        showing-table-editor = true;
    }

    show-color-stop-picker => {
        PickerData.set-current-stop-as-color();
        showing-color-stop-picker = true;
    }

    hide-color-stop-picker => {
        showing-color-stop-picker = false;
    }

    apply-current-value(text) => {
        Api.set-code-binding(
                current-element-information.source-uri,
                current-element-information.source-version,
                current-element-information.offset,
                current-property-information.name,
                text);
    }

    update-preview-value(text) => {
        if WindowManager.showing-table-editor {
            TableData.set-color-preview("\"\{text}\"");
        } else {
            self.possible_error = Api.set-json-preview-data(current-property-container-id, current-preview-data.name, "\"\{text}\"", check-new-value(current-property-container-id + current-preview-data.name));
        }
    }

    update-brush-preview() => {
        if widget-mode == WidgetMode.preview {
            brush-string = Api.as-slint-brush(PickerData.current-brush-kind, PickerData.current-angle, PickerData.current-brush, PickerData.current-gradient-stops);

            if WindowManager.showing-table-editor {
                TableData.set-color-preview("\"\{brush-string}\"");
            } else {
                self.possible_error = Api.set-json-preview-data(current-property-container-id, current-preview-data.name, "\"\{brush-string}\"", check-new-value(current-property-container-id + current-preview-data.name));
            }
        }
    }

    hide-floating-color-widget() => {
        if showing-table-editor {
            showing-color-picker = false;
            showing-color-stop-picker = false;
        } else {
            hide-floating-widget();
        }
    }

    property <string> container-property-name;
    function check-new-value(value: string) -> bool {
        if container-property-name == value {
            return false;
        } else {
            container-property-name = value;
            return true;
        }
    }

}
